/*  GStruct: data structure helpers for Java
 *  Copyright (C) 2011 Giulio Franco
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package gstruct.extensions;

/**
 * Collection of standard predicates 
 * @author Giulio Franco <giulio_franco@msn.com>
 *
 */
public final class Predicates {
	
	static final Predicate<Object> NULL = new Predicate<Object> () {
		@Override
		public boolean appliesTo(Object obj) {
			return (obj == null);
		}
	};
	static final Predicate<Object> NONNULL = new Predicate<Object>() {
		@Override
		public boolean appliesTo(Object obj) {
			return (obj != null);
		}
	};
	
	private static final class OrPredicate<T> implements Predicate<T> {

		private final Predicate<? super T>[] preds;
		
		@SafeVarargs
		public OrPredicate(Predicate<? super T>... predicates) {
			this.preds = predicates;
		}
		
		@Override
		public boolean appliesTo(T obj) {
			for (Predicate<? super T> p : preds) {
				if (p.appliesTo(obj)) {
					return true;
				}
			}
			return false;
		}
		
	}
	
	private static final class AndPredicate<T> implements Predicate<T> {

		private final Predicate<? super T>[] preds;
		
		@SafeVarargs
		public AndPredicate(Predicate<? super T>... predicates) {
			this.preds = predicates;
		}
		
		@Override
		public boolean appliesTo(T obj) {
			for (Predicate<? super T> p : preds) {
				if (!p.appliesTo(obj)) {
					return false;
				}
			}
			return true;
		}
		
	}
	
	private static final class XorPredicate<T> implements Predicate<T> {

		private final Predicate<? super T>[] preds;
		
		@SafeVarargs
		public XorPredicate(Predicate<? super T>... predicates) {
			this.preds = predicates;
		}
		
		@Override
		public boolean appliesTo(T obj) {
			boolean res = false;
			for (Predicate<? super T> p : preds) {
				res ^= p.appliesTo(obj);
			}
			return res;
		}
		
	}
	
	private static final class NotPredicate<T> implements Predicate<T> {

		private final Predicate<? super T> pred;
		
		public NotPredicate(Predicate<? super T> predicate) {
			this.pred = predicate;
		}
		
		@Override
		public boolean appliesTo(T obj) {
			return !pred.appliesTo(obj);
		}
		
	}
	
	private static final class NandPredicate<T> implements Predicate<T> {

		private final Predicate<? super T>[] preds;
		
		@SafeVarargs
		public NandPredicate(Predicate<? super T>... predicates) {
			this.preds = predicates;
		}
		
		@Override
		public boolean appliesTo(T obj) {
			for (Predicate<? super T> p : preds) {
				if (!p.appliesTo(obj)) {
					return true;
				}
			}
			return false;
		}
		
	}
	
	private static final class NorPredicate<T> implements Predicate<T> {

		private final Predicate<? super T>[] preds;
		
		@SafeVarargs
		public NorPredicate(Predicate<? super T>... predicates) {
			this.preds = predicates;
		}
		
		@Override
		public boolean appliesTo(T obj) {
			for (Predicate<? super T> p : preds) {
				if (p.appliesTo(obj)) {
					return false;
				}
			}
			return true;
		}
		
	}
	
	private Predicates() { }
	
	/**
	 * Predicate that applies to null objects
	 * @return Instance of Null predicate
	 */
	@SuppressWarnings("unchecked")
	public static <T> Predicate<T> nullPredicate() {
		return (Predicate<T>)NULL;
	}
	
	/**
	 * Predicate that applies to non-null objects
	 * @return Instance of Non-Null predicate
	 */
	@SuppressWarnings("unchecked")
	public static <T> Predicate<T> nonnullPredicate() {
		return (Predicate<T>)NONNULL;
	}
	
	private static void checkPredicates(Object... objs) {
		for (Object o : objs) {
			if (!(o instanceof Predicate)) {
				throw new ClassCastException(o + " is not a predicate");
			} 
		}
	}
	
	/**
	 * Predicate that applies to objects for which at least one
	 * of the passed predicates apply
	 * @param predicates	Predicated to be or-ed
	 * @return Instance of OR predicate
	 */
	@SafeVarargs
	public static <T> Predicate<T> or(Predicate<? super T>... predicates) {
		checkPredicates((Object[])predicates);
		return new OrPredicate<>(predicates);
	}
	
	/**
	 * Predicate that applies to objects for which all
	 * of the passed predicates apply
	 * @param predicates	Predicates to be and-ed
	 * @return Instance of AND predicate
	 */
	@SafeVarargs
	public static <T> Predicate<T> and(Predicate<? super T>... predicates) {	
		checkPredicates((Object[])predicates);
		return new AndPredicate<>(predicates);
	}
	
	/**
	 * Predicate that applies to objects for which
	 * an odd number of the passed predicates apply.
	 * @param predicates	Predicates to be xor-ed
	 * @return Instance of XOR predicate
	 */
	@SafeVarargs
	public static <T> Predicate<T> xor(Predicate<? super T>... predicates) {
		checkPredicates((Object[])predicates);
		return new XorPredicate<>(predicates);
	}
	
	/**
	 * Predicate that applies to objects for which
	 * the given predicate does not apply
	 * @param predicate		Predicate to be NOT-ed
	 * @return Instance of NOT predicate
	 */
	public static <T> Predicate<T> not(Predicate<? super T> predicate) {
		return new NotPredicate<>(predicate);
	}
	
	/**
	 * Predicate that applies to objects for which at least one
	 * of the passed predicates does not apply
	 * @param predicates	Predicated to be nand-ed
	 * @return Instance of NAND predicate
	 */
	@SafeVarargs
	public static <T> Predicate<T> nand(Predicate<? super T>... predicates) {	
		checkPredicates((Object[])predicates);
		return new NandPredicate<>(predicates);
	}
	
	/**
	 * Predicate that applies to objects for which all
	 * of the passed predicates do not apply.
	 * Equivalent to <pre>Predicates.not(Predicates.or(predicates))</pre>
	 * @param predicates	Predicates to be nor-ed
	 * @return Instance of NOR predicate
	 */
	@SafeVarargs
	public static <T> Predicate<T> nor(Predicate<? super T>... predicates) {	
		checkPredicates((Object[])predicates);
		return new NorPredicate<>(predicates);
	}
}
